home *** CD-ROM | disk | FTP | other *** search
/ Internet Publisher's Toolbox 2.0 / Internet Publisher's Toolbox.iso / internet / ntserver / wtsource / irfiles.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-11-14  |  67.1 KB  |  2,240 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE:
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.
  4.  
  5.    Brewster@think.com
  6. */
  7.  
  8. /* This file defines the files of an inverted file index.
  9.  *
  10.  * This structure is designed to be flexible rather than particularly
  11.  * optimized for speed or space.
  12.  * Thus this organization can support:
  13.  *   boolean, proximity, weights, and relevance feedback.
  14.  *
  15.  * Ported directly from the Lisp version 1.2 of the search engine.
  16.  *
  17.  * -brewster 6/90
  18.  */
  19.  
  20. #ifndef lint
  21. static char *RCSid = "$Header: /usr/local/ls63/pfeifer/freeWAIS-0.1-s/ir/RCS/irfiles.c,v 1.4 1993/07/13 19:22:33 huynh1 Exp $";
  22. #endif
  23.  
  24. /* ==================== */
  25. /* ===  Change Log  === */
  26. /*Created 12/4/89 Brewster full lisp version
  27.  *split from ir-engine 1/11/90 brewster
  28.  *
  29.  *added memory indexing for efficiency
  30.  *added variable index block sizes
  31.  *5/90 ported to C
  32.  *5/90 split from irbuild.c
  33.  *7/90 declared truename() a static function - HWM 
  34.  *7/90 changed filename table and headline table to be null
  35.  *     terminated in the file rather than \newline.
  36.  *     compatibility problems between systems (sigh).
  37.  *     -brewster
  38.  *7/90 added field to document table for WAIStation
  39.  *     -brewster
  40.  *7/90 fixed: BUG: when adding words to the word disk hashtable, watch out 
  41.  *     for the end of the file and wrap.  If it is full, error out.
  42.  *3/91 took out utilities and created futil.c -brewster
  43.  *3/91 took out the inverted file and created irinv.c -brewster
  44.  *
  45.  * $Log: irfiles.c,v $
  46.  * Revision 1.5  1993/07/01  19:34:50  warnock
  47.  * explicit declaration of gSavepart in savePartMatch
  48.  *
  49.  * Revision 1.4  93/07/01  19:18:54  warnock
  50.  * gethostname -> mygethostname
  51.  * 
  52.  * Revision 1.3  1993/02/16  17:07:49  freewais
  53.  * added AT&T patches for keyword list
  54.  *
  55.  * Revision 1.2  1993/02/16  15:32:56  freewais
  56.  * changed directory of servers registration to cnidr.org
  57.  *
  58.  * Revision 1.1  1993/02/16  15:05:35  freewais
  59.  * Initial revision
  60.  *
  61.  * Revision 1.63  92/04/28  16:54:41  morris
  62.  * added boolean support
  63.  * 
  64.  * Revision 1.62  92/03/20  13:57:04  jonathan
  65.  * New and Improved server registration.
  66.  * 
  67.  * Revision 1.61  92/03/19  10:38:27  shen
  68.  * modified lock to prevent more than one indexing at the same time.
  69.  * modified lock to block query while initilaizing a database
  70.  * 
  71.  * Revision 1.60  92/03/19  09:33:35  morris
  72.  * fixed the dictionary header to accurately indicate the number of blocks
  73.  * 
  74.  * Revision 1.59  92/02/27  12:25:27  shen
  75.  * add in locks
  76.  * 
  77.  * Revision 1.58  92/02/25  16:42:28  jonathan
  78.  * Added find_pointer_in_block using binary search from
  79.  * ses@techunix.technion.ac.il. (part of wais-8-b3-ses).
  80.  * 
  81.  * 
  82.  * Revision 1.57  92/02/25  12:49:16  jonathan
  83.  * removed a bunch of \n's from waislog's.
  84.  * 
  85.  * Revision 1.56  92/02/17  16:23:58  jonathan
  86.  * Modified build_catalog so it passes over the first entry (which seems to be
  87.  * empty).
  88.  * 
  89.  * Revision 1.55  92/02/17  12:37:34  jonathan
  90.  * Added code to build a catalog containing all headlines and DocID's for
  91.  * documents in the database.
  92.  * 
  93.  * Revision 1.54  92/02/16  09:50:49  jonathan
  94.  * plugged some memory leaks.  I bet there are more.
  95.  * 
  96.  * Revision 1.53  92/02/16  09:26:39  jonathan
  97.  * ask harry.
  98.  * 
  99.  * Revision 1.52  92/02/12  13:25:12  jonathan
  100.  * Added "$Log" so RCS will put the log message in the header
  101.  * 
  102.  */
  103. /* ==================== */
  104.  
  105. /* ==================== */
  106. /*     To Do list       
  107.  *
  108.  * Implement a filename hashtable so that we can test quickly when
  109.  *   a file has been indexed.
  110.  * Free up all memory when we can. 
  111.  * Implement logrithmic merging
  112.  * 
  113.  * change DOC_TAB_ENTRY_FILENAME_ID_SIZE to 4 This must be in version 9
  114.  * change DOC_TAB_ENTRY_HEADLINE_ID_SIZE to 4 This must be in version 9
  115.  * change DOC_TAB_ENTRY_NUM_LINES_SIZE to 4 This must be in version 9
  116.  * change MAX_WORD_LENGTH to 15 This must be in version 9
  117.  */
  118.  
  119. /* A specification for this is called ir-engine.text in microsoft word. */
  120.  
  121. #include <string.h> /* for memset() */
  122.  
  123. #include "cutil.h"
  124. #include "irfiles.h"
  125. #include "panic.h"
  126. #include "ustubs.h" /* for strstr */
  127. #include "futil.h"
  128. #include "sockets.h"
  129. #include "version.h"
  130. #include "irext.h"
  131. #include "irlex.h" /* for MAX_WORD_LENGTH */
  132.  
  133. #include "lock.h"
  134.  
  135. #ifdef WIN32
  136.  boolean SetInterlock(char *,boolean,boolean);
  137.  void ResetInterlock(void);
  138.  int CloseRenameOpen(FILE**,char*,char*,char*);
  139.  void InitSockets(void);
  140.  void TermSockets(void);
  141. #endif
  142.  extern char* keyword[50];
  143.  extern short nKeys;
  144.  char *descript[1000];
  145.  short nDesLines = 0;
  146.  
  147. #define PRINT_AS_INDEXING false /* also defined in irtfiles.c and irhash.c */
  148.  
  149. /*      -------------------------------    */
  150. #define DOC_TAB_HEADER_SIZE 2
  151. #define DOC_TAB_MAXIMUM_ENTRIES 8192
  152. #define DOC_TAB_ENTRY_FILENAME_ID_SIZE 3
  153. #define DOC_TAB_ENTRY_START_CHAR_SIZE 4
  154. #define DOC_TAB_ENTRY_END_CHAR_SIZE 4
  155. #define DOC_TAB_ENTRY_HEADLINE_ID_SIZE 3
  156. #define DOC_TAB_ENTRY_DOC_LENGTH_SIZE 4
  157. #define DOC_TAB_ENTRY_NUM_LINES_SIZE 3
  158. #define DOC_TAB_ENTRY_DATE_SIZE 4
  159. #define DOC_TAB_ELEMENT_SIZE 25 /* sum of above sizes */
  160.  
  161. #define DICTIONARY_ENTRY_SIZE 29 /* sum of MAX_WORD_LENGTH, 1 ('\0'), 
  162.                     NEXT_INDEX_BLOCK_SIZE and
  163.                     NUMBER_OF_OCCURANCES_SIZE */
  164.  
  165.  
  166. #define FILENAME_TABLE_HEADER_SIZE 4
  167. #define HEADLINE_TABLE_HEADER_SIZE 4
  168. #ifdef BIO
  169. #define DELIMITERS_SIZE 4
  170. #endif
  171.  
  172. #define FILE_WRITE_DATE_SIZE 4
  173. #define NUMBER_OF_OCCURANCES_SIZE 4
  174. #define DOCUMENT_SCORE_LIMIT_SIZE 1
  175. #define DOCUMENT_SCORE_LIMIT 255  /* this is computed from DOCUMENT_SCORE_LIMIT_SIZE */
  176.  
  177. #define TIME_WAIT_QUERY_END 5
  178. #define TIMEOUT_WAIT_QUERY_END 45
  179.  
  180. static char* temp_dictionary_filename _AP((char* destination, database* db));
  181.  
  182. static long current_lock_type = INVALID_LOCK;
  183.  
  184. /*============================
  185.   ===   Database support   ===
  186.   ============================*/
  187.  
  188.  
  189. /* looks up the total word count in an existing dictionary. */
  190.  boolean look_up_total_word_count _AP((database *db));
  191.  boolean look_up_total_word_count(db)
  192. database *db;
  193. {
  194.   long word_count;
  195.   long answer = look_up_word_in_dictionary(DICTIONARY_TOTAL_SIZE_WORD,
  196.                        &word_count, db);
  197.   if(answer == 0){
  198.     waislog(WLOG_HIGH, WLOG_ERROR,
  199.         "error finding total_word_count in dictionary %s\n", 
  200.         db->database_file);
  201.     disposeDatabase(db);
  202.     return(false);
  203.   }
  204.   else if(answer < 0){
  205.     waislog(WLOG_HIGH, WLOG_ERROR,"total_word_count not found in dictionary\n.This is either an error,or the database is old.");
  206.     db->total_word_count = word_count;
  207.   }
  208.   else{
  209.     db->total_word_count = word_count;
  210.   }
  211.   /* printf("Total Words in DB: %ld\n", db->total_word_count); */
  212.   return(true);
  213. }
  214.  
  215.  
  216. database*   
  217. openDatabase(name,initialize,for_search)
  218. char* name;
  219. boolean initialize;
  220. boolean for_search;
  221. {
  222.   /* open a database (open all its files), and return an opaque object.
  223.      return NULL if there is an error
  224.    */
  225. #ifndef WIN32   
  226.   unsigned long pid;
  227.   long timeout;
  228. #endif  
  229.   char file[MAX_FILE_NAME_LEN + 1 ];
  230.   char tmpfile[MAX_FILE_NAME_LEN + 1];
  231.   char open_mode[4];
  232.   database* db = (database*)s_malloc((size_t)sizeof(database)); 
  233.   if (db == NULL){ 
  234.     waislog(WLOG_HIGH, WLOG_ERROR,
  235.         "can't make a database, out of memory.\n");
  236.     return(NULL);
  237.   }
  238.  
  239.   db->total_word_count = 0;
  240.  
  241.   if (for_search == true)  
  242.     strncpy(open_mode,"rb",3); /* read only for searching */
  243.   else 
  244.     strncpy(open_mode,"r+b",4); /* read/write for building */
  245.  
  246.   /* set the query parameter to the original name */
  247.   {
  248.     query_parameter_type parameters;
  249.     char **list;
  250.     list=(char **)s_malloc(2*sizeof(char*));
  251.     list[0]=s_strdup(name);
  252.     list[1]=NULL;
  253.     parameters.srcs = list;
  254.     set_query_parameter(SET_SELECT_SOURCE,¶meters);
  255.   }
  256.    
  257.   /* ask the backend where the database lives, but put in the 
  258.      directory information that we already have.  This changes
  259.      the 'name' variable. */
  260.   db->database_file = 
  261.     s_strdup(merge_pathnames(database_file(pathname_name(name)),
  262.                  pathname_directory(name, tmpfile)));
  263.  
  264. #ifdef WIN32
  265.   if ((boolean)SetInterlock(db->database_file,initialize,for_search)==false) return NULL;
  266. #else
  267.   if (for_search == true) {
  268.   
  269.     /* check and set appropriate locks */
  270.  
  271.     if( utlk_using_lock(db->database_file, LOCK_UPDATE) ) {
  272.       waislog(WLOG_HIGH, WLOG_ERROR,
  273.        "can't search the database as an update is currently running");
  274.         return(NULL);
  275.       }
  276.     if ( utlk_set_lock(db->database_file, LOCK_QUERY) )
  277.        current_lock_type = LOCK_QUERY;
  278.     else
  279.       waislog(WLOG_LOW, WLOG_INFO, "query lock can't be set");
  280.  
  281.     }
  282.  
  283.   else {
  284.  
  285.     if( utlk_using_lock_and_get_pid(db->database_file, LOCK_INDEX, &pid) &&
  286.         (pid != getpid()) ) {
  287.       waislog(WLOG_HIGH, WLOG_ERROR,
  288.        "an indexing is currently running on the database. Try again later.");
  289.         return(NULL);
  290.       }
  291.     if (  utlk_set_lock(db->database_file, LOCK_INDEX) )
  292.        current_lock_type = LOCK_INDEX;
  293.     else
  294.       waislog(WLOG_LOW, WLOG_INFO, "index lock can't be set");
  295.     if ( initialize == true ) {
  296.      /* wait for current query finishing off */
  297.      timeout = 0;
  298.      while ( utlk_using_lock(db->database_file, LOCK_QUERY) ) {
  299.       if ( timeout >= TIMEOUT_WAIT_QUERY_END ) {
  300.          waislog(WLOG_HIGH, WLOG_ERROR,
  301.              "timed out in waiting for a query to finish. Try again later.");
  302.          utlk_unset_lock(db->database_file, LOCK_INDEX);
  303.          return(NULL);
  304.          }
  305.       waislog(WLOG_LOW, WLOG_INFO,
  306.              "waiting for a query to finish to initialize the database...");
  307.       sleep(TIME_WAIT_QUERY_END);
  308.       timeout += TIME_WAIT_QUERY_END;
  309.       }
  310.      if (  utlk_set_lock(db->database_file, LOCK_UPDATE) )
  311.        current_lock_type = LOCK_UPDATE;
  312.      else
  313.        waislog(WLOG_LOW, WLOG_INFO, "update lock can't be set");
  314.      }
  315.  
  316.     }
  317. #endif /* WIN32 */
  318.   
  319.   if(initialize == true){
  320.     initialize_index_files(db);
  321.   } 
  322.   else  {
  323.     db->dictionary_stream = 
  324.       s_fopen(dictionary_filename(file, db),open_mode);
  325.     if (db->dictionary_stream == NULL){ 
  326.       waislog(WLOG_HIGH,WLOG_ERROR,"can't open the word hash file %s\n",file); 
  327.       disposeDatabase(db);
  328.       return(NULL);
  329.     }
  330.     /* find the total_word_count from the dictionary */
  331.     if(for_search){
  332.       if(false == look_up_total_word_count(db)) { /* side effects db */
  333.         disposeDatabase(db);
  334.     return(NULL);
  335.         }
  336.     }
  337.           
  338.     db->filename_table_stream =
  339.       s_fopen(filename_table_filename(file, db),open_mode);
  340.     if (db->filename_table_stream == NULL){ 
  341.       waislog(WLOG_HIGH, WLOG_ERROR,
  342.           "can't open the filename file %s", file); 
  343.       disposeDatabase(db);
  344.       return(NULL);
  345.     }
  346.         
  347.     db->headline_table_stream = 
  348.       s_fopen(headline_table_filename(file, db),open_mode);
  349.     if (db->headline_table_stream == NULL){
  350.       waislog(WLOG_HIGH, WLOG_ERROR,
  351.           "can't open the headline file %s", file); 
  352.       disposeDatabase(db);
  353.       return(NULL);
  354.     }
  355.  
  356. #ifdef BIO
  357.     db->delimiters_stream = 
  358.       s_fopen(delimiters_filename(file, db),open_mode);
  359.     if (db->delimiters_stream == NULL){
  360.       waislog(WLOG_HIGH, WLOG_ERROR,
  361.           "can't open the delimiters file %s, using defaults", file); 
  362.       /* disposeDatabase(db); */
  363.       /* return(NULL); */
  364.     }
  365. #endif
  366.  
  367.     db->document_table_stream = 
  368.       s_fopen(document_table_filename(file, db),open_mode);
  369.     if (db->document_table_stream == NULL){ 
  370.       waislog(WLOG_HIGH, WLOG_ERROR,
  371.           "can't open the document id file %s", file); 
  372.       disposeDatabase(db);
  373.       return(NULL);
  374.     }
  375.       
  376.     /* initialize the allocated entries variable */
  377.     s_fseek(db->document_table_stream, 0L, SEEK_END);
  378.     db->doc_table_allocated_entries = 
  379.       (ftell(db->document_table_stream) - DOC_TAB_HEADER_SIZE) 
  380.     / DOC_TAB_ELEMENT_SIZE;
  381.   }
  382.   db->index_file_number = 0;
  383.   ext_open_database(db,initialize,for_search);
  384.   return(db);
  385. }
  386.  
  387.  
  388. void        
  389. closeDatabase(db)
  390. database* db;
  391. /* close a database and all its files. Do not dispose of the structure. */
  392. {
  393.   if (db == NULL)
  394.     return;
  395.   close_dictionary_file(db);
  396.   if (db->dictionary_stream != NULL)
  397.     s_fclose(db->dictionary_stream);
  398.   if (db->filename_table_stream != NULL)
  399.     s_fclose(db->filename_table_stream);
  400.   if (db->headline_table_stream != NULL)
  401.     s_fclose(db->headline_table_stream);
  402.   if (db->document_table_stream != NULL)
  403.     s_fclose(db->document_table_stream);
  404.   if (db->index_stream != NULL)
  405.     s_fclose(db->index_stream);
  406.   ext_close_database(db);
  407. #ifdef WIN32
  408.   ResetInterlock();
  409. #else
  410.   utlk_unset_lock(db->database_file, current_lock_type);
  411.   if ( current_lock_type == LOCK_UPDATE)
  412.      utlk_unset_lock(db->database_file, LOCK_INDEX);
  413.   current_lock_type = INVALID_LOCK;
  414. #endif /* WIN32 */
  415. }
  416.  
  417. void 
  418. disposeDatabase(db)
  419. database* db;
  420. {
  421.     closeDatabase(db);
  422.     s_free(db->database_file);
  423.     s_free(db);
  424. }
  425.  
  426. /* ==================================== */
  427. /* ===  Initialization of the files === */
  428. /* ==================================== */
  429.  
  430. #define BLOCK_SIZE 16384 /* size of blocks of zeros to write to a file */
  431.  
  432. static FILE* initialize_file _AP((long size,char* filename,boolean zero_it));
  433.  
  434. static FILE* initialize_file(size,filename,zero_it)
  435. long size;
  436. char* filename;
  437. boolean zero_it;
  438. /* initializes a file by opening a new stream, making it the right
  439.  * size and returning the stream.
  440.  */
  441. {
  442.   FILE* file = NULL;
  443.   long i;
  444.  
  445. #ifdef ANSI_LIKE
  446.   remove(filename);
  447. #endif
  448.  
  449.   file = s_fopen(filename, "wb");
  450.   if(NULL == file){ 
  451.     panic("The file %s could not be opened\n", filename);
  452.   }
  453.  
  454.   if(zero_it){
  455.     if(size >= BLOCK_SIZE){ /* then write big blocks of zeros */
  456.       char* zeros = NULL;
  457.       zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  458.       if(NULL == zeros){
  459.     panic("Could not allocate a large block of Zeros\n");
  460.       }
  461.       memset(zeros, 0, BLOCK_SIZE);
  462.       while(size >= BLOCK_SIZE){    
  463.     /* then write big blocks of zeros */
  464.     if(BLOCK_SIZE != fwrite(zeros, 1, BLOCK_SIZE, file))
  465.       panic("Write failed");
  466.     size = size - BLOCK_SIZE;
  467.       }
  468.       s_free(zeros);
  469.     }
  470.     for(i = 0; i < size; i++){  /* clean up the rest */
  471.       putc('\0', file); 
  472.     }
  473.   } 
  474.   else{             /* dont zero it */
  475.     grow_file(file, size);
  476.   } 
  477.  
  478. #ifdef THINK_C
  479.   /* set the mac file type to INDX */
  480.   setFileType(filename, WAIS_INDEX_FILE_TYPE, CREATOR);
  481. #endif              /* THINK_C */
  482.  
  483.   s_fclose(file);
  484.   file = s_fopen(filename, "r+b"); /* open it in read/write */
  485.   if(NULL == file){
  486.     panic("Error in initialization, can not reopen %s.\n", filename);
  487.   }
  488.   return(file);
  489. }
  490.  
  491. void initialize_index_files (db)
  492. database* db;
  493. /* This creates new index files, deleting any old ones. */
  494. {
  495.   char file[MAX_FILENAME_LEN];
  496.  
  497.   /* cprintf(PRINT_AS_INDEXING, "initializing index files: %s\n", db->database_file); */
  498.  
  499.   remove(dictionary_filename(file, db)); /* remove the old one */
  500.  
  501.   db->index_stream = NULL;
  502.  
  503.   db->doc_table_allocated_entries = 1; /* the 0th is the null pointer */
  504.   db->document_table_stream =
  505.     initialize_file((DOC_TAB_HEADER_SIZE + DOC_TAB_ELEMENT_SIZE),
  506.             document_table_filename(file, db), TRUE);
  507.   db->filename_table_stream =
  508.     initialize_file(FILENAME_TABLE_HEADER_SIZE,
  509.             filename_table_filename(file, db), TRUE);
  510.   db->headline_table_stream =
  511.     initialize_file(HEADLINE_TABLE_HEADER_SIZE,
  512.             headline_table_filename(file, db), TRUE);
  513. #ifdef BIO
  514.   db->delimiters_stream =
  515.     initialize_file(DELIMITERS_SIZE,
  516.             delimiters_filename(file, db), TRUE);
  517. #endif
  518. }
  519.  
  520. /* ========================= */
  521. /* ===  Dictionary File  === */
  522. /* ========================= */
  523.  
  524. /* The dictionary file is a 1 deep tree of blocks.  
  525.    The header of the file says how long the header block is.
  526.    The "header block" is a set of pointers to the heads of
  527.    the blocks in the dictionary.
  528.  
  529.    A dictionary block is a list of word and pointer pairs.  The words
  530.    are padded to a fixed length so that it is a fixed length record.
  531.    The pointers are pointers into the inverted file (except in the header
  532.    block where they are pointers into the dictionary file).
  533. */
  534.  
  535. /*  SEARCHING DICTIONARY FILES */
  536.    
  537. /* top level function:
  538.    long look_up_word_in_dictionary(char *word, long *word_id, database* db) 
  539.  */
  540.  
  541. unsigned char *dictionary_header_block = NULL; /* the dictionary header. 
  542.                           loaded once */
  543.  
  544. long number_of_dictionary_blocks = 0;  /* also the length of the dictionary 
  545.                       header block */
  546.  
  547. unsigned char *dictionary_block = NULL; /* this is one of the dict blocks */
  548.  
  549. int dictionary_last_word_occurances; /* This is a temporary hack so I can 
  550.                     separate out the relevance feedback 
  551.                     changes for posting. DON'T USE THIS 
  552.                     ANYWHERE - IT'LL BE GONE SOON
  553.                       */
  554.  
  555. void close_dictionary_file(db)
  556.      database *db;
  557. {
  558.   if(dictionary_header_block) s_free(dictionary_header_block);
  559.   dictionary_header_block = NULL;
  560. }
  561.   
  562.   
  563. static long fread_from_stream _AP((FILE* stream,unsigned char* buf,
  564.                    long nbytes));
  565.  
  566. static long fread_from_stream(stream,buf,nbytes)
  567. FILE *stream;
  568. unsigned char *buf;
  569. long nbytes;
  570. /* this is a safe version of unix 'fread' it does all the checking
  571.  * and looping necessary
  572.  */
  573. {
  574.   long didRead;
  575.   long toRead = nbytes;
  576.   long totalRead = 0;       /* paranoia */
  577.   /*printf("in Fread_from_stream buffer %ld, nbytes %ld\n", (long)buf, nbytes); */
  578.  
  579.   while (toRead > 0){
  580.     didRead = fread(buf, sizeof(char), toRead, stream);
  581.     if(didRead == -1)       /* error*/
  582.       return(-1);
  583.     if(didRead == 0)        /* eof */
  584.       return(-2);       /* maybe this should return 0? */
  585.     toRead -= didRead;
  586.     buf += didRead;
  587.     totalRead += didRead;
  588.   }
  589.   if(totalRead != nbytes)   /* we overread for some reason */
  590.     return(- totalRead);    /* bad news */    
  591.   return(totalRead);
  592. }
  593.  
  594. #ifdef DICT_FUNC
  595. char *dictionary_block_word(i,block)
  596. long i;
  597. unsigned char *block;
  598. /* returns the word field in the ith dictionary block entry */
  599. {
  600.   return((char *)(block + (i * DICTIONARY_ENTRY_SIZE)));
  601. }
  602.  
  603. long dictionary_block_position(i,block)
  604. long i;
  605. unsigned char *block;
  606. /* returns the position field in the ith dictionary block entry */
  607. {
  608.   /* printf("dictionary_block_position %ld\n",
  609.      read_bytes_from_memory
  610.      (NEXT_INDEX_BLOCK_SIZE,
  611.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  612.       MAX_WORD_LENGTH + 1)); */
  613.   return(read_bytes_from_memory
  614.      (NEXT_INDEX_BLOCK_SIZE,
  615.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  616.       MAX_WORD_LENGTH + 1));
  617. }
  618.  
  619. long dictionary_block_word_occurances(i,block)
  620. long i;
  621. unsigned char *block;
  622. /* returns the occurances field in the ith dictionary block entry */
  623. {
  624.   return(read_bytes_from_memory
  625.      (NEXT_INDEX_BLOCK_SIZE,
  626.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  627.       MAX_WORD_LENGTH + 1 + NEXT_INDEX_BLOCK_SIZE));
  628. }
  629. #endif
  630.  
  631.  
  632. #ifdef PARTIALWORD
  633.  
  634. typedef struct {
  635.     long  blocknum, wordcount;
  636.     } saveparttype;
  637.     
  638. static long gMaxpart = 0;
  639. static long gNpart = 0;
  640. static long gAtpart = 0;
  641. static saveparttype *gSavepart = NULL;
  642.  
  643. void clearPartMatch()
  644. {
  645.   if (gSavepart!=NULL) free(gSavepart);
  646.   gSavepart= NULL;
  647.   gMaxpart= 0;
  648.   gNpart= 0;
  649.   gAtpart= 0;
  650. }
  651.  
  652. void savePartMatch( blocknum, wordcount)
  653. long  blocknum, wordcount;
  654. {
  655.    if (gNpart>=gMaxpart) {
  656.      gMaxpart= gNpart + 100;
  657.      if (gSavepart==NULL) /* (saveparttype*) */ gSavepart= (saveparttype*)malloc(gMaxpart*sizeof(saveparttype));
  658.      else /* (saveparttype*) */ gSavepart= (saveparttype*)realloc(gSavepart, gMaxpart*sizeof(saveparttype));
  659.      }
  660.    gSavepart[gNpart].blocknum= blocknum;
  661.    gSavepart[gNpart].wordcount= wordcount;
  662.    gNpart++;
  663. }
  664. #endif
  665.  
  666. static long find_pointer_in_block _AP((char* word,unsigned char* block,
  667.                        long block_length,
  668.                        long *position,
  669.                        boolean findpart
  670.                        ));
  671.  
  672. /* Courtesy of Simon Spero <ses@techunix.technion.ac.il> */
  673.  
  674. static long find_pointer_in_block(word,block,block_length, position, findpart)
  675. char *word;
  676. unsigned char *block;
  677. long block_length; /* in entries */ 
  678. long *position;
  679. boolean findpart;  /* dgg, partial word match */
  680. /* returns 0 if an error or if the word is below the lowest block,
  681.    (this confusion between error and NULL is bad, but found late in the 
  682.    design process)
  683.    it returns the positive position if the word is there exactly,
  684.    and the negative of the position of the word before it if the
  685.    word is not there exactly.
  686.    position is set with the entry postion in the block that the word was 
  687.    found.  This is used for searching.
  688. */
  689. {
  690.   /* find the entry in the dictionary header for this word.
  691.      returns 0 if not found. */
  692.   /* this could be binary search XXX */
  693. #ifdef WIN32
  694.   long i,high,low;
  695. #else
  696.   long i,high,low,tmp;
  697. #endif
  698. #ifdef PARTIALWORD
  699.   long  wordlen= strlen(word);
  700. #endif
  701.  
  702.   low = 0;
  703.   high = block_length;
  704.   i = (low+high)/2;
  705.   while(low != high) {
  706.     long compare;
  707.     char *dictionary_word = dictionary_block_word(i, block);
  708. /* 
  709.     printf("dw = %s, w = %s, low = %d, i = %d, hi = %d\n",
  710.        dictionary_word,word,low,i,high);
  711.  */
  712.     if(dictionary_word[0] == '\0') {
  713.       if(high != i) {
  714.     high = i;
  715.     i = (low+i)/2;
  716.       } else {
  717.     *position = i-1;
  718.     return(- dictionary_block_position(i-1,block));
  719.       }
  720.     } else {
  721. #ifdef PARTIALWORD
  722.      if (findpart) {
  723.        compare = strncmp(dictionary_word, word, wordlen);
  724.        if ((0 == compare) ) {
  725.         int  ati = i;
  726.     /* save partword matches for later... */
  727.     savePartMatch( dictionary_block_position(i, block),
  728.                dictionary_block_word_occurances(i,block));
  729.     while (i>0 && 0 == compare) {
  730.       --i;
  731.       dictionary_word = dictionary_block_word(i, block);
  732.           compare = strncmp(dictionary_word, word, wordlen);
  733.       if (0 == compare) savePartMatch( dictionary_block_position(i, block),
  734.                            dictionary_block_word_occurances(i,block));
  735.       }
  736.     i= ati;
  737. /* Could be
  738.         compare = 0;
  739.         while (i<block_length && 0 == compare) {
  740.  */
  741.         while (i<high && 0 == compare) {
  742.           ++i;
  743.           dictionary_word = dictionary_block_word(i, block);
  744.           compare = strncmp(dictionary_word, word, wordlen);
  745.           if (0 == compare) savePartMatch( dictionary_block_position(i, block),
  746.                                            dictionary_block_word_occurances(i,block));
  747.           }
  748.         *position = ati;
  749.         return(-dictionary_block_position(ati, block));
  750.         }
  751.        }
  752.      else compare = strcmp(dictionary_word, word);
  753. #else
  754.      compare = strcmp(dictionary_word, word);
  755. #endif      
  756.  
  757.       if(0 == compare) {
  758.     dictionary_last_word_occurances = 
  759.       dictionary_block_word_occurances(i,block);
  760.     *position = i;
  761.     return(dictionary_block_position(i, block));
  762.       }
  763.       if(compare > 0){
  764.     if(high != i) {
  765.       high = i;
  766.       i = (low+i)/2;
  767.       } else {
  768.         *position = i-1;
  769.         return(- dictionary_block_position(i-1 , block));
  770.       }
  771.       } else {
  772.     if (low != i) {
  773.       low = i;
  774. #ifdef WIN32
  775.       i = (long)((0.5+high+i)/2);
  776. #else
  777.       i = (0.5+high+i)/2;
  778. #endif
  779.     } else {
  780.       *position = i;
  781.       return(- dictionary_block_position(i , block));
  782.     }
  783.       }
  784.     }
  785.   }
  786.   if(i == 0) {
  787.     *position = 0;
  788.     return(0);
  789.   }
  790.   else {
  791.     *position = i-1;
  792.     return(- dictionary_block_position(i - 1, block));
  793.   }
  794. }
  795.  
  796. unsigned char *read_dictionary_block(block,position,length,stream)
  797. unsigned char *block;
  798. long position;
  799. long length;
  800. FILE *stream;
  801. /* reads the dictionary block from the disk and returns it.
  802.    block is the place to put it, if it is NULL, then it is malloc'ed.
  803.    position is the position in the dictionary file to start reading.
  804.    length is th enumber of entries (not bytes) in the block.
  805.    stream is the dictionary stream.
  806.    
  807.    it returns NULL if it loses.
  808.  */
  809.     
  810. {
  811.   static long last_position = -1;
  812.   static unsigned char* last_block = NULL;
  813.   static FILE* last_dict_file = NULL; /* there may be more than one dict */
  814.  
  815.   if (stream != last_dict_file)
  816.    { /* invalidate the cache */
  817.      last_position = -1;
  818.      last_dict_file = stream;
  819.    }
  820.   
  821.   if(NULL == block)
  822.     block = (unsigned char *)s_malloc((size_t)(length*DICTIONARY_ENTRY_SIZE));
  823.  
  824.   if ((block != last_block) || 
  825.       (position != last_position)) {
  826.     last_position = position;
  827.     last_block = block;
  828.     s_fseek(stream, position, SEEK_SET);
  829.     if(0 > fread_from_stream(stream, block, (length * DICTIONARY_ENTRY_SIZE))){
  830.       waislog(WLOG_HIGH, WLOG_ERROR,
  831.           "Could not read the dictionary block %ld, length %ld",
  832.           block, length);
  833.       return(NULL);
  834.     }
  835.   }
  836.   return(block);
  837. }
  838.  
  839.  
  840. #ifdef PARTIALWORD
  841.  
  842. long 
  843. look_up_partialword_in_dictionary(word, number_of_occurances, db)
  844. char *word;
  845. long *number_of_occurances;
  846. database* db;
  847. {
  848.   long answer;
  849.   boolean findpart = false;
  850.  
  851.   if (word != NULL) {
  852.     clearPartMatch();
  853.     answer= look_up_word_in_dictionary( word, number_of_occurances, db);
  854.     if (answer > 0) return (answer); /* got a match */
  855.     }
  856.     
  857.   if (gAtpart >= gNpart) {
  858.     clearPartMatch();
  859.     return(-1);
  860.     }
  861.   else {
  862.     answer= gSavepart[gAtpart].blocknum;
  863.     if (NULL != number_of_occurances) {
  864.       if (answer > 0) *number_of_occurances = gSavepart[gAtpart].wordcount;
  865.       else *number_of_occurances = 0;
  866.       }
  867.     gAtpart++;
  868.     return( answer);
  869.     }
  870. }
  871. #endif
  872.  
  873. long 
  874. look_up_word_in_dictionary(word, number_of_occurances, db)
  875. char *word;
  876. long *number_of_occurances;
  877. database* db;
  878. /* looks up the word in the dictionary file. Returns the pointer
  879.    into the inverted file or negative number if not found, 
  880.    or 0 if error.
  881.     It sets number_of_occurances (if it is not NULL) to the number
  882.       registered in the file.  This is used during searching.  
  883.       It is set to 0 if error or word not found.
  884.       If it is NULL, then it is not touched.
  885.  */
  886. {
  887.   long position;
  888.   long answer;
  889.   FILE *stream = db->dictionary_stream;
  890.   long dictionary_block_pos;
  891.   boolean findpart = false; /* dgg, PARTIALWORD flag */
  892.  
  893. #ifdef PARTIALWORD
  894.   {
  895.     int l = strlen(word) - 1;
  896.     if (l > 0 && word[l] == PARTWORD_WILDCARD) {
  897.       findpart= true;
  898.       word[l]= '\0';
  899.       }
  900.   }
  901. #endif
  902.  
  903.   if(NULL == dictionary_header_block)
  904.    {
  905.      s_fseek(stream, 0L, SEEK_SET);
  906.      number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE,stream);
  907.      dictionary_header_block =
  908.        read_dictionary_block(dictionary_header_block,DICTIONARY_HEADER_SIZE,
  909.                  number_of_dictionary_blocks,stream);
  910.      if(NULL == dictionary_header_block)
  911.       { waislog(WLOG_HIGH, WLOG_ERROR,
  912.         "Could not read dictionary header block in db %s.",
  913.         db->database_file);
  914.     return(0);
  915.       }
  916.    }
  917.  
  918.   dictionary_block_pos = 
  919.     find_pointer_in_block(word,
  920.                dictionary_header_block,
  921.                number_of_dictionary_blocks,
  922.                &position, false);
  923.   if(0 == dictionary_block_pos)
  924.    { /* waislog(WLOG_HIGH, WLOG_ERROR, "Could not find pointer for word '%s' (location %ld) in block in db %s!",
  925.          word, word, db->database_file); */
  926.      return(-1);  /* not an error, necessarily if the word is before the first entry */
  927.    }
  928.  
  929.   dictionary_block = 
  930.     read_dictionary_block(dictionary_block,ABS(dictionary_block_pos),
  931.               DICTIONARY_BLOCK_SIZE,stream);
  932.   if(NULL == dictionary_block)
  933.    { waislog(WLOG_HIGH, WLOG_ERROR,
  934.          "Could not read dictionary block %ld in db %s",
  935.          ABS(dictionary_block_pos),
  936.          db->database_file);
  937.      return(0);
  938.    }
  939.   answer = find_pointer_in_block(word, dictionary_block, 
  940.                  DICTIONARY_BLOCK_SIZE, &position, findpart);
  941.   if((NULL != number_of_occurances)) {
  942.     if (answer > 0)
  943.     *number_of_occurances = 
  944.       dictionary_block_word_occurances(position, dictionary_block);
  945.     else
  946.       *number_of_occurances = 0;
  947.   }
  948.  
  949.   return(answer);
  950. }
  951.  
  952.  
  953. /*  BUILDING DICTIONARY FILES */
  954.  
  955.  
  956. long number_of_dictionary_entries; /* number allocated */
  957.  
  958. char *block_of_zeros = NULL;
  959.  
  960. static void write_zeros_to_stream _AP((long n_bytes,FILE* stream));
  961.  
  962. static void write_zeros_to_stream(n_bytes,stream)
  963. long n_bytes;
  964. FILE *stream;
  965. /* writes zeros to a file quickly */
  966. {   
  967.   long i;
  968.   if(n_bytes >= BLOCK_SIZE){    /* then write big blocks of zeros */
  969.     if(NULL == block_of_zeros){
  970.       block_of_zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  971.       memset(block_of_zeros, 0, BLOCK_SIZE);
  972.     }
  973.     while(n_bytes >= BLOCK_SIZE){   
  974.       /* then write big blocks of zeros */
  975.       if(BLOCK_SIZE != 
  976.      fwrite(block_of_zeros, sizeof(char), BLOCK_SIZE, stream))
  977.     panic("Write failed");
  978.       n_bytes -= BLOCK_SIZE;
  979.     }
  980.   }
  981.   for(i = 0; i < n_bytes; i++){ /* clean up the rest */
  982.     putc('\0', stream); 
  983.   }
  984. }   
  985.  
  986. /* returns 0 if successful */
  987. long init_dict_file_for_writing(db)
  988. database *db;
  989. {
  990.   char filename[MAX_FILENAME_LEN];
  991.  
  992.   if (db->dictionary_stream != NULL)
  993.     fclose(db->dictionary_stream);
  994.   db->dictionary_stream = 
  995.     s_fopen(temp_dictionary_filename(filename, db), "w+b");
  996.  
  997.   db->total_word_count = 0;
  998.   init_dict_file_detailed(db->dictionary_stream,db->number_of_words);
  999.   return(0);
  1000. }
  1001.  
  1002. static long dict_number_of_blocks _AP((long number_of_words));
  1003.  
  1004. static long
  1005. dict_number_of_blocks(number_of_words)
  1006. long number_of_words;
  1007. {
  1008.   long number_of_blocks;
  1009.   number_of_blocks = (number_of_words / DICTIONARY_BLOCK_SIZE) +
  1010.     ((0 == (number_of_words % DICTIONARY_BLOCK_SIZE)) ? 0 : 1);
  1011.   return(number_of_blocks);
  1012. }
  1013.  
  1014. void
  1015. record_num_blocks_in_dict(dictionary_stream,number_of_words)
  1016. FILE* dictionary_stream;
  1017. long number_of_words;
  1018. { /* write the number of blocks */
  1019.   s_fseek(dictionary_stream, 0L, SEEK_SET);
  1020.   write_bytes(dict_number_of_blocks(number_of_words),
  1021.           DICTIONARY_HEADER_SIZE, dictionary_stream);
  1022.   fseek(dictionary_stream, 0L, SEEK_END);
  1023. }
  1024.  
  1025. void
  1026. init_dict_file_detailed(dictionary_stream,number_of_words)
  1027. FILE* dictionary_stream;
  1028. long number_of_words;
  1029. {
  1030.   /* create space for the table in the front of the file */
  1031.   write_zeros_to_stream(DICTIONARY_HEADER_SIZE + 
  1032.             (DICTIONARY_ENTRY_SIZE * 
  1033.              dict_number_of_blocks(number_of_words)),
  1034.             dictionary_stream);
  1035.   record_num_blocks_in_dict(dictionary_stream,number_of_words);
  1036.   number_of_dictionary_entries = 0;
  1037. }
  1038.  
  1039. /* this must be called in alphabetical order, and writes the word to
  1040.    the dictionary file. */  
  1041. long add_word_to_dictionary(word,position,number_of_occurances,db)
  1042. char *word;
  1043. long position;
  1044. long number_of_occurances;
  1045. database *db;
  1046.      /* Puts a word into the dictionary file. */
  1047. {
  1048.   /* assumes the stream has been initialized, and it is positioned
  1049.      at the end */
  1050.   FILE *stream = db->dictionary_stream;
  1051.   char padded_word[MAX_WORD_LENGTH + 1];
  1052.  
  1053.   memset(padded_word, 0, MAX_WORD_LENGTH + 1); /* clear the word */
  1054.   strcpy(padded_word, word);
  1055.  
  1056.   if(0 == (number_of_dictionary_entries % DICTIONARY_BLOCK_SIZE)){
  1057.     /* then add an entry in the header */
  1058.     long original_position = s_ftell(stream);
  1059.     long header_entry = number_of_dictionary_entries / DICTIONARY_BLOCK_SIZE; 
  1060.     /* printf("Adding header entry %ld %s original pos %ld\n", 
  1061.        header_entry, padded_word, original_position); */
  1062.     fseek(stream, DICTIONARY_HEADER_SIZE + 
  1063.       (header_entry * DICTIONARY_ENTRY_SIZE), SEEK_SET);
  1064.     if((MAX_WORD_LENGTH + 1) != 
  1065.        fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  1066.       panic("Write failed");
  1067.     write_bytes(original_position, NEXT_INDEX_BLOCK_SIZE, stream);
  1068.     write_bytes(0L, NUMBER_OF_OCCURANCES_SIZE, stream);
  1069.     fseek(stream, original_position, SEEK_SET); /* go back to the end */
  1070.     /* zero the next block */
  1071.     write_zeros_to_stream(DICTIONARY_ENTRY_SIZE * DICTIONARY_BLOCK_SIZE,
  1072.               stream); 
  1073.     fseek(stream, original_position, SEEK_SET);      
  1074.   }
  1075.   /* write the word */  
  1076.   if((MAX_WORD_LENGTH + 1) !=
  1077.      fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  1078.     panic("Write failed");
  1079.   write_bytes(position, NEXT_INDEX_BLOCK_SIZE, stream);
  1080.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, stream);
  1081.   number_of_dictionary_entries++;   
  1082.   db->total_word_count += number_of_occurances;
  1083.   return(0);
  1084. }
  1085.  
  1086. /* this is called after all add_words are done, but before the file 
  1087.    is closed. Returns 0 if successful. */
  1088. long
  1089. finished_add_word_to_dictionary(db)
  1090.      database* db;
  1091. {
  1092.   char temp_filename[MAX_FILENAME_LEN];
  1093.   char filename[MAX_FILENAME_LEN];
  1094.  
  1095.   waislog(WLOG_LOW, WLOG_INFO, "Total word count for dictionary is: %ld",
  1096.       db->total_word_count);
  1097.   if(0 != add_word_to_dictionary(DICTIONARY_TOTAL_SIZE_WORD, 
  1098.                 1, db->total_word_count, db))
  1099.     return(-1);
  1100.  
  1101.   record_num_blocks_in_dict(db->dictionary_stream,db->number_of_words);
  1102.  
  1103.   fflush(db->dictionary_stream); /* so that any new opens will see a 
  1104.                     valid file */
  1105.  
  1106.   /* rename the .dcttmp file to dct */
  1107.   temp_dictionary_filename(temp_filename, db);
  1108.   dictionary_filename(filename, db);
  1109.   /* printf("renaming %s to %s\n", temp_filename, filename); */
  1110. #ifdef WIN32
  1111.   if (0!=(int)CloseRenameOpen(&(db->dictionary_stream),temp_filename,filename,"r+b"))
  1112.     waislog(WLOG_HIGH, WLOG_ERROR,
  1113.         "could not rename file %s to %s",
  1114.         temp_filename, filename);
  1115. #else
  1116.   if(0 != rename(temp_filename, filename))
  1117.     waislog(WLOG_HIGH, WLOG_ERROR,
  1118.         "could not rename file %s to %s",
  1119.         temp_filename, filename);
  1120. #endif /* WIN32 */
  1121.   return(0);
  1122. }
  1123.   
  1124. void print_dictionary_block(block,size)
  1125. unsigned char *block;
  1126. long size;
  1127. /* this prints the contents of a dictionary block */
  1128. {
  1129.   long i;
  1130.   for(i = 0; i < size; i++){
  1131.     char *word = dictionary_block_word(i, block);
  1132.     if(word[0] == '\0')
  1133.       break;
  1134.     /* I assume this is only for debugging - JG */
  1135.     printf("Entry %3ld: %21s %7ld %7ld\n", i, word,
  1136.         dictionary_block_position(i, block),
  1137.         dictionary_block_word_occurances(i, block));
  1138.   }
  1139. }
  1140.  
  1141. void print_dictionary _AP((database* db));
  1142.   
  1143. void print_dictionary(db)
  1144. database *db;
  1145. {
  1146.   /* prints the contents of a dictionary */
  1147.   FILE *stream = db->dictionary_stream;
  1148.   long i;
  1149.   long new_number_of_dictionary_blocks;
  1150.  
  1151.   if(NULL == stream)
  1152.     panic("dictionary stream is not open");
  1153.   s_fseek(stream, 0L, SEEK_SET);
  1154.   new_number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE, stream);
  1155.   if(new_number_of_dictionary_blocks > number_of_dictionary_blocks)
  1156.     dictionary_header_block = NULL;
  1157.   number_of_dictionary_blocks = new_number_of_dictionary_blocks;
  1158.   printf("Number of dictionary blocks %ld\n", number_of_dictionary_blocks);
  1159.   if(NULL == (dictionary_header_block =
  1160.           read_dictionary_block(dictionary_header_block,
  1161.                     DICTIONARY_HEADER_SIZE,
  1162.                     number_of_dictionary_blocks,
  1163.                     stream)))
  1164.     panic("Could not read dictionary header block");
  1165.   printf("The Dictionary Header Block:\n");
  1166.   print_dictionary_block(dictionary_header_block, number_of_dictionary_blocks);
  1167.   for(i = 0; i < number_of_dictionary_blocks; i++){
  1168.     long pos = dictionary_block_position(i, dictionary_header_block);
  1169.     if(NULL == (dictionary_block =
  1170.         read_dictionary_block(dictionary_block,
  1171.                       pos, DICTIONARY_BLOCK_SIZE, stream)))
  1172.       panic("Could not read dictionary block %ld", pos);
  1173.     printf("\n\nDictionary block %ld (position %ld):\n", i, pos);
  1174.     print_dictionary_block(dictionary_block, DICTIONARY_BLOCK_SIZE);
  1175.   }
  1176.   fseek(stream, 0L, SEEK_END);
  1177. }
  1178.  
  1179. #ifdef testing
  1180. /* dictionary testing code */
  1181.  
  1182. static void check_dictionary_entry _AP((char* word,long expected_position,
  1183.                     database* db));
  1184.  
  1185. static void check_dictionary_entry(word,expected_position,db)
  1186. char *word;
  1187. long expected_position;
  1188. database *db;
  1189. {
  1190.   if(expected_position != look_up_word_in_dictionary(word, NULL, db)) {
  1191.     waislog(WLOG_HIGH, WLOG_ERROR,
  1192.         "%s should be %ld is %ld in db %s", 
  1193.         word, expected_position, 
  1194.         look_up_word_in_dictionary(word, NULL, db),
  1195.         db->database_file);
  1196.   }
  1197. }
  1198.   
  1199. static void test_dictionary _AP((database* db));
  1200.  
  1201. static void test_dictionary(db)
  1202. database *db;
  1203. /* this is just an trivial test */
  1204. {
  1205.  
  1206.   db->number_of_words = 3;
  1207.   init_dict_file_for_writing(db);
  1208.   add_word_to_dictionary("aardvark", 123L, 0l, db);
  1209.   add_word_to_dictionary("house", 234L, 0L, db);
  1210.   add_word_to_dictionary("mary", 345L, 0L, db);
  1211.   fflush(db->dictionary_stream);
  1212.   print_dictionary(db);
  1213.   check_dictionary_entry("aardvark", 123L, db);
  1214.   check_dictionary_entry("house", 234L, db);
  1215.   check_dictionary_entry("mary", 345L, db);
  1216.   check_dictionary_entry("food", -123L, db);
  1217.   check_dictionary_entry("zebra", -345L, db);
  1218.   check_dictionary_entry("aaarf", 0L, db);
  1219. }
  1220. #endif /* def testing */
  1221.  
  1222.  
  1223. /*========================*
  1224.  *===  Document Table  ===*
  1225.  *========================*/
  1226.  
  1227. boolean
  1228. read_document_table_entry(doc_entry,number,db)
  1229. document_table_entry* doc_entry;
  1230. long number;
  1231. database* db;
  1232. /* returns a document_table_entry on the stack */
  1233. {
  1234.   long position;
  1235.   FILE *stream = db->document_table_stream;
  1236.     
  1237.   position = (DOC_TAB_HEADER_SIZE + 
  1238.           ((long)number * (long)DOC_TAB_ELEMENT_SIZE));
  1239.  
  1240.   if (0 != fseek(stream, position, SEEK_SET))
  1241.     { 
  1242.       waislog(WLOG_HIGH, WLOG_ERROR,
  1243.           "fseek failed into the document table to position %ld in db %s", 
  1244.           position,
  1245.           db->database_file);
  1246.       return(false);
  1247.     }
  1248.     
  1249.   doc_entry->filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, 
  1250.                      stream);
  1251.   doc_entry->headline_id = read_bytes(DOC_TAB_ENTRY_HEADLINE_ID_SIZE, 
  1252.                      stream);   
  1253.   doc_entry->start_character = 
  1254.     read_bytes(DOC_TAB_ENTRY_START_CHAR_SIZE, stream);
  1255.   doc_entry->end_character = 
  1256.     read_bytes(DOC_TAB_ENTRY_END_CHAR_SIZE, stream);
  1257.   doc_entry->document_length = 
  1258.     read_bytes(DOC_TAB_ENTRY_DOC_LENGTH_SIZE, stream);
  1259.   doc_entry->number_of_lines = 
  1260.     read_bytes(DOC_TAB_ENTRY_NUM_LINES_SIZE, stream);
  1261.   doc_entry->date = 
  1262.     read_bytes(DOC_TAB_ENTRY_DATE_SIZE, stream);
  1263.   if (doc_entry->date == EOF) { 
  1264.     return(false);
  1265.   }
  1266.  
  1267. /*printf("read_document_table_entry pos %ld val %lx\n",position,doc_entry->date);*/
  1268.  
  1269.   return(true);
  1270. }
  1271.  
  1272.  
  1273. boolean
  1274. writeUserValToDocIDTable(userVal,doc,db)
  1275. unsigned long userVal;
  1276. long doc;
  1277. database* db;
  1278. /* the docIDTable needs to keep a user value for use by other indexing
  1279.    systems.  Currently it is stuffed in the date field. 
  1280.  
  1281.    This routine needs to be updated if read_document_table_entry changes
  1282.  */
  1283. {
  1284.   long position;
  1285.   
  1286.   position = (DOC_TAB_HEADER_SIZE +
  1287.           ((long)doc * (long)DOC_TAB_ELEMENT_SIZE) +
  1288.           DOC_TAB_ENTRY_FILENAME_ID_SIZE +
  1289.           DOC_TAB_ENTRY_HEADLINE_ID_SIZE + 
  1290.           DOC_TAB_ENTRY_START_CHAR_SIZE +
  1291.           DOC_TAB_ENTRY_END_CHAR_SIZE +
  1292.           DOC_TAB_ENTRY_DOC_LENGTH_SIZE +
  1293.           DOC_TAB_ENTRY_NUM_LINES_SIZE);
  1294.  
  1295.   if (0 != fseek(db->document_table_stream,position,SEEK_SET))
  1296.    { waislog(WLOG_HIGH, WLOG_ERROR,
  1297.          "fseek failed into the document table to position %ld in db %s", 
  1298.          position,db->database_file);
  1299.      return(false);
  1300.    }
  1301.  
  1302. /*printf("writeUserValToDocIDTable pos %ld val %lx\n",position,userVal);*/
  1303.  
  1304.   write_bytes(userVal,DOC_TAB_ENTRY_DATE_SIZE,db->document_table_stream);
  1305.   fflush(db->document_table_stream);
  1306.   return(true);
  1307. }
  1308.  
  1309.  
  1310.  
  1311. #ifdef testing
  1312.  
  1313. static  boolean check_document_id _AP((long doc_id,database* db));
  1314.  
  1315. static  boolean
  1316. check_document_id(doc_id,db)
  1317. long doc_id;
  1318. database* db;
  1319. /* returns true if that is a valid doc_id (corresponds to a file
  1320.    that has not been deleted */
  1321. {
  1322.   long position;
  1323.   FILE *stream = db->document_table_stream;
  1324.   long filename_id;
  1325.   char filename[MAX_FILE_NAME_LEN];
  1326.  
  1327.   position = (DOC_TAB_HEADER_SIZE + 
  1328.           ((long)doc_id * (long)DOC_TAB_ELEMENT_SIZE));
  1329.  
  1330.   if (0 != fseek(stream, position, SEEK_SET)) { 
  1331.     waislog(WLOG_HIGH, WLOG_ERROR,
  1332.         "fseek failed into the document table to position %ld in db %s",
  1333.         position,
  1334.         db->database_file);
  1335.     return(false);
  1336.   }
  1337.     
  1338.   filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, stream);
  1339.   /* probe the file.  Is there a faster way? */
  1340.   return(probe_file_possibly_compressed(read_filename_table_entry(filename_id, filename,NULL,db)));
  1341. }
  1342. #endif
  1343.  
  1344. long write_document_table_entry(doc_table_entry, db)
  1345. document_table_entry* doc_table_entry;
  1346. database* db;
  1347. {
  1348.   /* returns the document_id */
  1349.   s_fseek(db->document_table_stream,
  1350.          (DOC_TAB_HEADER_SIZE +
  1351.           (db->doc_table_allocated_entries *
  1352.            DOC_TAB_ELEMENT_SIZE)),
  1353.          SEEK_SET);
  1354.   /* write the pieces */
  1355.   write_bytes(doc_table_entry->filename_id,
  1356.           DOC_TAB_ENTRY_FILENAME_ID_SIZE,
  1357.           db->document_table_stream);
  1358.   write_bytes(doc_table_entry->headline_id,
  1359.           DOC_TAB_ENTRY_HEADLINE_ID_SIZE,
  1360.           db->document_table_stream);
  1361.   write_bytes(doc_table_entry->start_character,
  1362.           DOC_TAB_ENTRY_START_CHAR_SIZE,
  1363.           db->document_table_stream);
  1364.   write_bytes(doc_table_entry->end_character,
  1365.           DOC_TAB_ENTRY_END_CHAR_SIZE,
  1366.           db->document_table_stream);
  1367.   write_bytes(doc_table_entry->document_length,
  1368.           DOC_TAB_ENTRY_DOC_LENGTH_SIZE,
  1369.           db->document_table_stream);
  1370.   /*  printf("Writing %ld lines\n", document_table_entry->number_of_lines); */
  1371.   write_bytes(doc_table_entry->number_of_lines,
  1372.           DOC_TAB_ENTRY_NUM_LINES_SIZE,
  1373.           db->document_table_stream);
  1374.   write_bytes(doc_table_entry->date,
  1375.           DOC_TAB_ENTRY_DATE_SIZE,
  1376.           db->document_table_stream);
  1377.   db->doc_table_allocated_entries++;
  1378.   return(db->doc_table_allocated_entries);
  1379. }
  1380.  
  1381. long next_document_id(db)
  1382. database* db;
  1383. {
  1384.   return(db->doc_table_allocated_entries);
  1385. }
  1386.  
  1387.  
  1388. /*========================*
  1389.  *===  Filename table  ===*
  1390.  *========================*/
  1391.  
  1392. #ifndef MAXPATHLEN /* think_c does not define it for instance */
  1393. #define MAXPATHLEN 2000
  1394. #endif /* MAXPATHLEN */
  1395.  
  1396. static char *read_filename_table_stream _AP((long position, 
  1397.                         char* filename,
  1398.                         char* type, 
  1399.                         time_t* file_write_date,
  1400.                         FILE *stream));
  1401.  
  1402. static char *read_filename_table_stream(position,filename,type,
  1403.                     file_write_date, stream)
  1404. long position;
  1405. char* filename;
  1406. char* type;
  1407. time_t* file_write_date;
  1408. FILE *stream;
  1409. {
  1410.   /* Returns the filename array after side effecting it,
  1411.    *  or NULL if an error.
  1412.    * The type of the file is put in the argument "type".  This will
  1413.    * not be longer than MAX_FILE_NAME_LEN.
  1414.    *
  1415.    * if type is NULL then ignore it,
  1416.    * if file_write_date is NULL then ignore it,
  1417.    * If position is -1, then it does not seek.
  1418.    *
  1419.    * Leave the file positioned at the start of the next entry.
  1420.    */   
  1421.   long file_write_date_internal;
  1422.   char type_internal[MAX_TYPE_LEN];
  1423.  
  1424.   if(NULL == stream)
  1425.     return(NULL);
  1426.  
  1427.   if(NULL == type)  /* this means we do not care, so set up a dummy */
  1428.     type = type_internal;
  1429.  
  1430.   filename[0] = '\0';   /* init to the empty string */
  1431.   if(NULL != type)
  1432.     type[0] = '\0'; /* init to the empty string */
  1433.  
  1434.   if(position != -1){
  1435.     if (0 != fseek(stream, position, SEEK_SET)){
  1436.       waislog(WLOG_HIGH, WLOG_ERROR, "fseek failed into the filename index to position %ld", 
  1437.           position);
  1438.       return(NULL);
  1439.     }
  1440.   }
  1441.   if(false == read_string_from_file(stream, filename, MAX_FILE_NAME_LEN)){
  1442.     return(NULL);
  1443.   }
  1444.   else{
  1445.     file_write_date_internal = read_bytes(FILE_WRITE_DATE_SIZE, stream);
  1446.     if(file_write_date){
  1447.       *file_write_date = (time_t)file_write_date_internal;
  1448.     }
  1449.     if(false == read_string_from_file(stream, type, MAX_TYPE_LEN)){
  1450.       return(NULL);
  1451.     } 
  1452.   }
  1453.   return(filename);
  1454. }
  1455.      
  1456. char *read_filename_table_entry(position,filename,type,file_write_date,db)
  1457. long position;
  1458. char* filename;
  1459. char* type;
  1460. time_t* file_write_date;
  1461. database* db;
  1462. {
  1463.   /* Returns the filename array after side effecting it,
  1464.    *  or NULL if an error.
  1465.    * The type of the file is put in the argument "type".  This will
  1466.    * not be longer than MAX_FILE_NAME_LEN.
  1467.    *
  1468.    * if type is NULL then ignore it,
  1469.    * if file_write_date is NULL then ignore it,
  1470.    * If position is -1, then it does not seek.
  1471.    *
  1472.    * Leave the file positioned at the start of the next entry.
  1473.    */   
  1474.   FILE *stream = db->filename_table_stream;
  1475.   return(read_filename_table_stream(position,filename,type,
  1476.                     file_write_date,stream));
  1477. }
  1478.  
  1479. long write_filename_table_entry(filename,type,db)
  1480. char* filename;
  1481. char *type;
  1482. database* db;
  1483. {
  1484.   /* writes the filename (NULL terminated),
  1485.      followed by 4 bytes of creation date,
  1486.      followed by the file type (NULL terminated),
  1487.      Returns the postion of the filename
  1488.      */
  1489.   long free_position,count,i,j;
  1490.   char full_path[MAXPATHLEN];
  1491.   char savedFileName[MAX_FILENAME_LEN + 1];
  1492.   char* tmp_type = NULL;   /* temporary type */
  1493.   char* tmp_type_pointer = NULL;   /* temporary type pointer */
  1494.  
  1495.   s_fseek(db->filename_table_stream, 0L, SEEK_END);
  1496.   free_position = ftell(db->filename_table_stream);
  1497.   /* add the filename to the hashtable not done yet XXX
  1498.      (setf (gethash filename *filename_table_hashtable*)
  1499.      (file_write_date filename))
  1500.      */
  1501.   fprintf(db->filename_table_stream, "%s", truename(filename, full_path));
  1502.   fputc(0, db->filename_table_stream);
  1503.   if(FILE_WRITE_DATE_SIZE != sizeof(time_t)){ /* check if these are the same */
  1504.     panic("We have a problem with the file_write_date_size\n");
  1505.   }
  1506.   write_bytes((long)file_write_date(filename),
  1507.               FILE_WRITE_DATE_SIZE, db->filename_table_stream);
  1508.  
  1509. /*  fwrite(type, sizeof(char), strlen(type) + 1, db->filename_table_stream);*/
  1510.  
  1511.  
  1512. /* francois - multitype extensions */
  1513. /* 
  1514.    Here we just add the document types to the file entry, we need to 
  1515.    check to see if each file is there so we probe them.
  1516. */
  1517.  
  1518.    if ( strstr(type,",") == NULL ) {
  1519.       fprintf(db->filename_table_stream, "%s",type);
  1520.       fputc(0,db->filename_table_stream);
  1521.    }
  1522.    else {
  1523.    
  1524.        /* count up the number of document types */
  1525.        count = 1L;
  1526. #ifdef WIN32
  1527.        for (i = 0L; i < (long)strlen(type); i++){
  1528. #else       
  1529.        for (i = 0L; i < strlen(type); i++){
  1530. #endif       
  1531.           if ( type[i] == ',' )
  1532.              count++;
  1533.        }
  1534.  
  1535.            /* duplicate the type and save the pointer */
  1536.            tmp_type = s_strdup(type);
  1537.            tmp_type_pointer = tmp_type;
  1538.  
  1539.       
  1540.            /* append types - NULL out the pointer so that strtok can grab the subsequent entries */
  1541.        for (i = 0L; i < count; i++ ) {
  1542.           tmp_type_pointer = s_strdup(strtok(tmp_type_pointer,","));
  1543.           
  1544.           strcpy(savedFileName,filename);
  1545.               if ( strcmp(savedFileName+(strlen(savedFileName)-2), ".Z") == 0 ) {
  1546.                /* it's a .Z file.  First, remove the suffix or many things get confused. */
  1547.                  savedFileName[(strlen(savedFileName)-2)] = 0;
  1548.               }
  1549.          
  1550.               /* strip the current extension, but not the period */
  1551.               for ( j = strlen(savedFileName); j >= 0L; j-- ) {
  1552.                  if (savedFileName[j] == '.') {
  1553.                     savedFileName[j+1] = 0;
  1554.                     break;
  1555.                  }
  1556.               }
  1557.          
  1558.               /* append the type to the file name */
  1559.               strcat(savedFileName,tmp_type_pointer);
  1560.  
  1561.       
  1562.               if(probe_file_possibly_compressed(savedFileName)) {
  1563.  
  1564.                  fprintf(db->filename_table_stream, "%s",tmp_type_pointer);
  1565.                  fprintf(db->filename_table_stream, ",");
  1566.               }
  1567.               
  1568.               s_free(tmp_type_pointer);
  1569.           tmp_type_pointer = NULL;
  1570.       
  1571.        }
  1572.        
  1573.        /* release the tmp_type allocations */
  1574.        s_free(tmp_type);
  1575.      
  1576.            /* terminate the string */
  1577.            fputc(0,db->filename_table_stream);
  1578.  
  1579.    }
  1580.  
  1581.  
  1582.  
  1583.  
  1584.   return(free_position);
  1585. }
  1586.  
  1587. /* functions to figure out if the file is in the index already */
  1588.         
  1589. static boolean filename_in_filename_stream _AP((char *filename, char *type, 
  1590.                         time_t *file_write_date, 
  1591.                         FILE *stream));
  1592.              
  1593. static boolean filename_in_filename_stream(filename, type, 
  1594.                        file_write_date, stream)
  1595. char *filename;
  1596. char *type;
  1597. time_t *file_write_date;
  1598. FILE *stream;
  1599.      /* returns true if it is there (and side effects type and 
  1600.       file_write_date). 
  1601.         leaves the stream at the end of the file.
  1602.     If type or file_write_date is NULL, then it is a dont care.
  1603.     type, if it is an array, should be MAX_FILENAME_LEN long at least.
  1604.     */
  1605. {
  1606.   /* this is slow because it loops through the whole file every time.
  1607.      this might want to be optimized by making a hashtable. */
  1608.   char next_filename[MAX_FILENAME_LEN];
  1609.   
  1610.   s_fseek(stream, FILENAME_TABLE_HEADER_SIZE, SEEK_SET);
  1611.   while(!feof(stream)){
  1612.     char new_type[MAX_FILENAME_LEN];
  1613.     if(NULL == 
  1614.        read_filename_table_stream(-1, next_filename, new_type, 
  1615.                   file_write_date, stream))
  1616.       return(false);
  1617. #ifdef WIN32
  1618.     if(0 == _stricmp(next_filename, filename))
  1619. #else
  1620.     if(0 == strcmp(next_filename, filename))
  1621. #endif
  1622.       return(true);
  1623.   }
  1624. }
  1625.  
  1626. boolean filename_in_database(filename,type,file_write_date,db)
  1627. char *filename;
  1628. char *type;
  1629. time_t *file_write_date;
  1630. database *db;
  1631. {
  1632.   return(filename_in_filename_stream(filename, type, file_write_date, 
  1633.                      db->filename_table_stream));
  1634. }
  1635.  
  1636. /* this caches the last filename that was found to be in the filename file,
  1637.    this way repeated attempts to figure out if a file is there will be fast.
  1638.    This is the case when retrieving successive blocks of a file. */   
  1639. char last_filename_found_in_file[MAX_FILE_NAME_LEN];
  1640. char last_filename_file[MAX_FILE_NAME_LEN];
  1641.  
  1642. boolean filename_in_filename_file(filename,type,file_write_date, filename_file)
  1643. char *filename;
  1644. char *type;
  1645. time_t *file_write_date;
  1646. char *filename_file;
  1647. {
  1648.   if(NULL == filename)
  1649.     return(false);
  1650.   
  1651.   if(0 == strcmp(last_filename_found_in_file, filename) &&
  1652.      0 == strcmp(last_filename_file, filename_file))
  1653.     return(true);
  1654.   else
  1655. #ifdef WIN32
  1656.    { FILE *stream = s_fopen(filename_file, "rb");
  1657. #else
  1658.    { FILE *stream = s_fopen(filename_file, "r");
  1659. #endif
  1660.      boolean answer;
  1661.  
  1662.      if(NULL == stream)
  1663.       { s_fclose(stream);
  1664.     return(false);
  1665.       }
  1666.      answer = 
  1667.        filename_in_filename_stream(filename,type,file_write_date, stream);
  1668.      if(answer == true)
  1669.       { /* record it in the cache */
  1670.     strncpy(last_filename_file, filename_file, MAX_FILE_NAME_LEN);
  1671.     strncpy(last_filename_found_in_file, filename, MAX_FILE_NAME_LEN);
  1672.       }
  1673.      s_fclose(stream);
  1674.      return(answer);
  1675.    }
  1676. }
  1677.  
  1678.  
  1679. /*========================*
  1680.  *===  Headline Table  ===*
  1681.  *========================*/
  1682.  
  1683. char *read_headline_table_entry(position,db)
  1684. long position;
  1685. database* db;
  1686.   /* returns the headline array after side effecting it.  Beware that 
  1687.    * the next call to this function will overwrite the the headline_array
  1688.    */
  1689. {
  1690.   /* this is the headline that gets returned */
  1691.   static char headline_array[MAX_HEADLINE_LEN]; 
  1692.   FILE *stream = db->headline_table_stream;
  1693.   headline_array[0] = '\0'; /* init to the empty string */
  1694.     
  1695.   if (0 != fseek(stream, position, SEEK_SET)) { 
  1696.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1697.         "fseek failed into the headline index to position %ld in db %s", 
  1698.         position, db->database_file);
  1699.     return(headline_array);
  1700.   }
  1701.   if(false == read_string_from_file(db->headline_table_stream, 
  1702.                     headline_array, MAX_FILE_NAME_LEN)){ 
  1703.    waislog(WLOG_HIGH, WLOG_ERROR, 
  1704.         "headline table is corrupt at %ld in db %s", 
  1705.         position, db->database_file);
  1706.   }
  1707.   return(headline_array);
  1708. }
  1709.  
  1710. /* writes the string to the file followed by a NULL.
  1711.  * The returned number is the position in the file to start reading.
  1712.  */
  1713. long write_headline_table_entry(headline,db)
  1714. char* headline;
  1715. database* db;
  1716. {
  1717.   /* writes the headline followed by a newline.
  1718.      Returns the postion of the headline.
  1719.      */
  1720.   long free_position;
  1721.   s_fseek(db->headline_table_stream, 0L, SEEK_END);
  1722.   free_position = ftell(db->headline_table_stream);
  1723.   /* printf("Headline position: %ld, next headline length: %ld\n",
  1724.      free_position, strlen(headline)); */
  1725.   fprintf(db->headline_table_stream, "%s", headline);
  1726.   fputc(0, db->headline_table_stream);
  1727.   return(free_position);
  1728. }
  1729.  
  1730. #ifdef BIO
  1731. /*========================*
  1732.  *=== delimiters - dgg ===*
  1733.  *========================*/
  1734.  
  1735. char *read_delimiters(db)
  1736. database* db;
  1737.   /* returns the word delimiters for a database.  Beware that 
  1738.    * the next call to this function will overwrite the the headline_array
  1739.    */
  1740. {
  1741.   static char delimiters[MAX_HEADLINE_LEN+1]; 
  1742.   FILE *stream = db->delimiters_stream;
  1743.   delimiters[0] = '\0'; /* init to the empty string */
  1744.         
  1745.   if(false == read_string_from_file(db->delimiters_stream, 
  1746.                     delimiters, MAX_HEADLINE_LEN)){ 
  1747.    waislog(WLOG_HIGH, WLOG_ERROR, 
  1748.         "delimiters are corrupt in db %s",  db->database_file);
  1749.   }
  1750.   /* need to weed out .dlm files that have no symbols... */
  1751.   if (delimiters[0] == '\0') return(NULL);
  1752.   else return(delimiters);
  1753. }
  1754.  
  1755. /* writes the string to the file followed by a NULL.
  1756.  * The returned number is the position in the file to start reading.
  1757.  */
  1758. long write_delimiters(delimiters,db)
  1759. char* delimiters;
  1760. database* db;
  1761. {
  1762.   /* writes the headline followed by a newline.
  1763.      Returns the postion of the headline.
  1764.      */
  1765.   long free_position;
  1766.   s_fseek(db->delimiters_stream, 0L, SEEK_SET); /* _SET, only one set of delims / file ? */
  1767.   free_position = ftell(db->delimiters_stream);
  1768.   fprintf(db->delimiters_stream, "%s", delimiters);
  1769.   fputc(0, db->delimiters_stream);
  1770.   return(free_position);
  1771. }
  1772. #endif
  1773.  
  1774. /* =================== */
  1775. /* === Source file === */
  1776. /* =================== */
  1777.  
  1778. /* the source file is an ascii file for describing a source.
  1779.    it is defined in ../doc/source.txt */
  1780.  
  1781. /* Registers the src structure with the directory of servers.
  1782.    Return true if successful */
  1783. boolean register_src_structure(filename)
  1784. char *filename;
  1785. {
  1786. #ifndef WIN32
  1787.   char string[200], *editor;
  1788.   long answer;
  1789. #endif
  1790.  
  1791. #ifdef WIN32
  1792.   printf("\n");
  1793.   printf("Please look over the source description in %s\n",filename);
  1794.   printf("Be sure it contains an IP address and DNS name, as well as\n");
  1795.   printf("the port you intend to use for the WAIS server.\n\n");
  1796.   printf("This program does not automatically register sources with the directory\n");
  1797.   printf("of servers.  You must mail the %s file manually\n",filename);
  1798.   printf("to the following addresses:\n");
  1799.   printf("    wais-directory-of-servers@cnidr.org\n");
  1800.   printf("    wais-directory-of-servers@quake.think.com\n");
  1801.   return true;
  1802.  
  1803. #else
  1804.   if((editor = (char*)getenv("EDITOR")) == NULL &&
  1805.      (editor = (char*)getenv("VISUAL")) == NULL) {
  1806.     printf("Could not get EDITOR environment variable.\n"); 
  1807.     printf("Please check over the source structure: %s\n", filename);
  1808.     printf("Then mail it to wais-directory-of-servers@cnidr.org\n");
  1809.     return (false);
  1810.   }
  1811.   /* register the server with the directory of servers */
  1812.   printf("Please look over the Source description.  Be sure it contains\n");
  1813.   printf("an IP address and DNS name, as well as the port you intend\n");
  1814.   printf("to use for the server.\n");
  1815.   printf("When you are finished it will be mailed to the directory of servers.\n");
  1816.   fflush(stdout);
  1817.  
  1818.   sprintf(string, "exec %s %s", editor, filename);
  1819.   system(string);
  1820.  
  1821.   printf("\nSending source structure to the CNIDR directory of servers...");
  1822.  
  1823.   sprintf(string,
  1824.       "cat %s | mail wais-directory-of-servers@cnidr.org %s\n", 
  1825.       filename, getenv("USER"));
  1826.  
  1827.   answer = system(string);
  1828.   printf("\nSending source structure to the TM directory of servers...");
  1829.   sprintf(string,
  1830.           "cat %s | mail wais-directory-of-servers@quake.think.com %s\n",
  1831.           filename, getenv("USER"));
  1832.  
  1833.   answer = system(string);
  1834.   printf("Done.\n");      
  1835.   return((answer == 0)?true:false);
  1836. #endif
  1837. }
  1838.  
  1839.  
  1840. /* Writes a source structure to a file.
  1841.    If the export_database arg is set, then the tcp_port is used in the 
  1842.    tcp-port slot.
  1843.    Returns true if successful. */
  1844. boolean write_src_structure(filename, database_name, typename, 
  1845.                 filenames, count, export_database, tcp_port)
  1846.      char *filename;
  1847.      char *database_name;
  1848.      char *typename;
  1849.      char **filenames;
  1850.      long count;
  1851.      boolean export_database;
  1852.      long tcp_port;
  1853. {
  1854.   long i,j;
  1855.   char hostname[120];
  1856.   struct hostent *h;
  1857.  
  1858. #ifndef THINK_C
  1859. #ifndef M_XENIX
  1860.  
  1861.   FILE *source_stream = s_fopen(filename, "w");
  1862.  
  1863.   fprintf(source_stream, "\n\n(:source \n");
  1864.   fprintf(source_stream, "   :version  3 \n");
  1865.   if(export_database){
  1866. #ifdef WIN32
  1867.     (void)InitSockets();
  1868.     hostname[0] = '\0';
  1869. #endif
  1870.     mygethostname(hostname, 120);
  1871.     h = gethostbyname(hostname);
  1872. #ifdef WIN32
  1873.     (void)TermSockets();
  1874. #endif
  1875.     if (h != NULL && 
  1876.     h->h_addr_list != NULL &&
  1877.     h->h_addr_list[0] != NULL) {
  1878.       fprintf(source_stream,
  1879.           "   :ip-address \"%d.%d.%d.%d\"\n",
  1880.           (unsigned char)h->h_addr_list[0][0], 
  1881.           (unsigned char)h->h_addr_list[0][1],
  1882.           (unsigned char)h->h_addr_list[0][2],
  1883.           (unsigned char)h->h_addr_list[0][3] );
  1884.     }
  1885.     fprintf(source_stream, "   :ip-name \"%s\"\n", hostname );
  1886.     fprintf(source_stream, "   :tcp-port %ld\n", tcp_port);
  1887.   }
  1888.   fprintf(source_stream, "   :database-name \"%s\"\n", database_name);
  1889.   fprintf(source_stream, "   :cost 0.00 \n");
  1890.   fprintf(source_stream, "   :cost-unit :free \n");
  1891.   fprintf(source_stream, "   :maintainer \"%s\"\n", 
  1892.       current_user_name());
  1893.   fprintf(source_stream, "   :keyword-list (\n");
  1894.   for (j=0; j< nKeys; j++) {
  1895.     fprintf(source_stream, "                  %s\n", keyword[j]);
  1896.   }
  1897.   fprintf(source_stream, "                  )\n");
  1898.  
  1899.   if(!nDesLines){
  1900.     fprintf(source_stream, "   :description \"Server created with %s on %s by %s\n",
  1901.             VERSION, printable_time(), current_user_name());
  1902.     if(count > 0){
  1903. #ifdef sgi
  1904.       fprintf(source_stream, "Files of type %s were used in the index.\n", typename);
  1905. #else
  1906.       fprintf(source_stream, "The files of type %s used in the index were:\n",
  1907.             typename);
  1908.       for(i = 0; i < count; i++){
  1909.         char full_path[MAX_FILENAME_LEN + 1];
  1910.         fprintf(source_stream, "   %s\n", truename(filenames[i], full_path));
  1911.       }
  1912. #endif
  1913.     }
  1914.     fprintf(source_stream, "\"\n");
  1915. } else
  1916.   for (j=0; j<nDesLines; j++)
  1917.     fprintf(source_stream, "%s", descript[j]);
  1918.   fprintf(source_stream, ")\n");      
  1919.   s_fclose(source_stream);
  1920.   
  1921. #endif /* ndef M_XENIX */
  1922. #endif /* ndef THINK_C */
  1923.  
  1924.   return(true);
  1925. }
  1926.  
  1927. boolean
  1928. build_catalog(db)
  1929. database* db;
  1930. {
  1931.   char catalog_name[MAX_FILENAME_LEN];
  1932.   document_table_entry doc_entry;
  1933.   char filename[MAX_FILE_NAME_LEN], type[100];
  1934.   FILE *catalog;
  1935.   long i;
  1936.  
  1937.   sprintf(catalog_name,"%s%s",db->database_file, catalog_ext);
  1938.   if((catalog = s_fopen(catalog_name, "w")) == NULL) {
  1939.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1940.         "Unable to open catalog file for write: %s.", catalog_name);
  1941.     return(false);
  1942.   }
  1943.  
  1944.   fprintf(catalog, "Catalog for database: %s\n", db->database_file);
  1945.   fprintf(catalog, "Date: %s\n", printable_time());
  1946.  
  1947.   /* the first document is empty - JG */
  1948.  
  1949.   fprintf(catalog, "%ld total document%s\n\n",
  1950.       db->doc_table_allocated_entries-1,
  1951.       (db->doc_table_allocated_entries==2) ? "":"s");
  1952.   
  1953.   for(i=1; i<db->doc_table_allocated_entries; i++) {
  1954. /*    fprintf(catalog, "Document # %ld\n", i); */
  1955.     if (read_document_table_entry(&doc_entry, i, db) 
  1956.     == true){
  1957.       char *hl;
  1958.       long hll;
  1959.       read_filename_table_entry(doc_entry.filename_id, 
  1960.                 filename,
  1961.                 type,
  1962.                 NULL,
  1963.                 db);
  1964.       fprintf(catalog, "Document # %ld Type: %s\n", i,type);
  1965.       hl = read_headline_table_entry(doc_entry.headline_id,db);
  1966.       hll = strlen(hl);
  1967.       fprintf(catalog, "Headline: %s", hl);
  1968.       if((hll== 0) || (hl[hll-1] != '\n')) fprintf(catalog,"\n");
  1969.  
  1970.       fprintf(catalog, "DocID: %d %d %s\n\n",
  1971.           doc_entry.start_character, doc_entry.end_character,
  1972.           filename);
  1973.     }
  1974.     else {
  1975.       fprintf(catalog, "Unable to read document table for document %n!\n\n", i);
  1976.     }
  1977.   }
  1978.   s_fclose(catalog);
  1979.   return(true);
  1980. }
  1981.  
  1982. /*****************************/
  1983. /***   Database support    ***/
  1984. /*****************************/
  1985.  
  1986. char* dictionary_filename(destination,db)
  1987. char* destination;
  1988. database* db;
  1989. {
  1990.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1991.   s_strncat(destination,dictionary_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1992.   return(destination);
  1993. }
  1994.  
  1995. /* for use in building so that the real one does not get overstomped */
  1996. static char* temp_dictionary_filename(destination,db)
  1997. char* destination;
  1998. database* db;
  1999. {
  2000.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2001.   s_strncat(destination,dictionary_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2002.   s_strncat(destination,"tmp",MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2003.   return(destination);
  2004. }
  2005.  
  2006. char* document_table_filename(destination,db)
  2007. char* destination;
  2008. database* db;
  2009. {
  2010.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2011.   s_strncat(destination,document_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2012.   return(destination);
  2013. }
  2014.  
  2015. char* filename_table_filename(destination,db)
  2016. char* destination;
  2017. database* db;
  2018. {
  2019.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2020.   s_strncat(destination,filename_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2021.   return(destination);
  2022. }
  2023.  
  2024. char* headline_table_filename(destination,db)
  2025. char* destination;
  2026. database* db;
  2027. {
  2028.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2029.     s_strncat(destination,headline_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2030.     return(destination);
  2031. }
  2032.  
  2033. #ifdef BIO
  2034. char* delimiters_filename(destination,db)
  2035. char* destination;
  2036. database* db;
  2037. {
  2038.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2039.     s_strncat(destination,delimiters_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2040.     return(destination);
  2041. }
  2042. #endif
  2043.  
  2044. char* index_filename(destination,db)
  2045. char* destination;
  2046. database* db;
  2047. {
  2048.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2049.     s_strncat(destination,index_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2050.     return(destination);
  2051. }
  2052.  
  2053. /* this is used during index creation.  if the version is -2, then it means
  2054.    the real index_filename.  This is a kludge */
  2055. char* index_filename_with_version(version,destination,db)
  2056. long version;
  2057. char* destination;
  2058. database* db;
  2059. {
  2060.   if(version == -2L){
  2061.     return(index_filename(destination, db));
  2062.   }
  2063.   else{
  2064.     sprintf(destination, "%s%s%ld", db->database_file,
  2065.         index_ext, version);
  2066.     return(destination);
  2067.   }
  2068. }
  2069.  
  2070.  
  2071. char* source_filename(destination,db)
  2072. char* destination;
  2073. database* db;
  2074. {
  2075.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  2076.   s_strncat(destination,source_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  2077.   return(destination);
  2078. }
  2079.  
  2080. char*
  2081. get_doc(destination, document_id, db, headline)
  2082. char* destination;
  2083. long document_id;
  2084. database* db;
  2085. boolean headline;
  2086. {
  2087.   document_table_entry doc_entry;
  2088.   char filename[MAX_FILE_NAME_LEN], type[100];
  2089.   char *hl;
  2090.  
  2091.   if (read_document_table_entry(&doc_entry, document_id, db) 
  2092.       == true){
  2093.     read_filename_table_entry(doc_entry.filename_id, 
  2094.                   filename,
  2095.                   type,
  2096.                   NULL,
  2097.                   db);
  2098.  
  2099. /* francois - multitype extension */
  2100.     if ( strstr(type,",") != NULL ) {
  2101.        type[strstr(type,",") - type] = '\0';
  2102.     }
  2103.  
  2104.  
  2105.     if (headline == TRUE) {
  2106.       hl = read_headline_table_entry(doc_entry.headline_id,db);
  2107.       sprintf(destination, "%d %d %s, \"%s\"", 
  2108.           doc_entry.start_character, doc_entry.end_character,
  2109.           filename, hl);
  2110.     }
  2111.     else
  2112.       sprintf(destination, "%d %d %s", 
  2113.           doc_entry.start_character, doc_entry.end_character,
  2114.           filename);
  2115.     return(s_strdup(type));
  2116.   }
  2117.   else return NULL;
  2118. }
  2119.  
  2120. long next_doc(destination, docID, db)
  2121. char* destination;
  2122. char* docID;
  2123. database* db;
  2124. {
  2125.   long i, start, end;
  2126.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  2127.   char *type, *loc;
  2128.  
  2129.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  2130.     if ((type = get_doc(doc, i, db, FALSE)) != NULL) {
  2131.       s_free(type);
  2132.       if (strcmp(doc, docID) == 0) {
  2133.     type = get_doc(doc, i+1, db, TRUE);
  2134.     sscanf(doc, "%d %d %s", &start, &end, fn);
  2135.     if((loc = strstr(doc, ",")) == NULL) return -1;
  2136.     fn[loc-doc] = 0;
  2137.     sprintf(destination, "%s, %s", doc, type);
  2138.     s_free(type);
  2139.     if( end != 0)
  2140.       return(end-start);
  2141.     else {  
  2142.       /* whole file, find file length from the file */
  2143.       long size;
  2144.       FILE* file = NULL;
  2145.       if (((file = s_fopen(fn, "r")) != NULL) &&
  2146.           (fseek(file, 0L, SEEK_END) == 0)  &&
  2147.           ((size = ftell(file)) != -1)) {
  2148.         s_fclose(file);
  2149.         return(size);   /* we are done, bytes is set */
  2150.       }
  2151.       else {
  2152.         s_fclose(file);
  2153.         return(-1);     /* something went wrong with the file */
  2154.       }
  2155.     }
  2156.       }
  2157.     }
  2158.   }
  2159.   return -1;
  2160. }
  2161.  
  2162. long previous_doc(destination, docID, db)
  2163. char* destination;
  2164. char* docID;
  2165. database* db;
  2166. {
  2167.   long i, start, end;
  2168.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  2169.   char *type, *loc;
  2170.  
  2171.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  2172.     if ((type = get_doc(doc, i, db, FALSE)) != NULL) {
  2173.       s_free(type);
  2174.       if (strcmp(doc, docID) == 0) {
  2175.     if (i != 0) {
  2176.       type = get_doc(doc, i-1, db, TRUE);
  2177.       sscanf(doc, "%d %d %s", &start, &end, fn);
  2178.       if((loc = strstr(doc, ",")) == NULL) return -1;
  2179.       fn[loc-doc] = 0;
  2180.       sprintf(destination, "%s, %s", doc, type);
  2181.       s_free(type);
  2182.       if( end != 0)
  2183.         return(end-start);
  2184.       else {    
  2185.         /* whole file, find file length from the file */
  2186.         long size;
  2187.         FILE* file = NULL;
  2188.         if (((file = s_fopen(fn, "r")) != NULL) &&
  2189.         (fseek(file, 0L, SEEK_END) == 0)  &&
  2190.         ((size = ftell(file)) != -1)) {
  2191.           s_fclose(file);
  2192.           return(size); /* we are done, bytes is set */
  2193.         }
  2194.         else {
  2195.           s_fclose(file);
  2196.           return(-1);   /* something went wrong with the file */
  2197.         }
  2198.       }
  2199.     }
  2200.       }
  2201.     }
  2202.   }
  2203.   return(-1);
  2204. }
  2205.  
  2206. long next_docid(docID, db)
  2207. char* docID;
  2208. database* db;
  2209. {
  2210.   long i;
  2211.   char doc[MAX_FILE_NAME_LEN+50];
  2212.  
  2213.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  2214.     if (get_doc(doc, i, db, FALSE) != NULL) {
  2215.       if (strcmp(doc, docID) == 0) {
  2216.     return (i+1);
  2217.       }
  2218.     }
  2219.   }
  2220.   return -1;
  2221. }
  2222.  
  2223. long previous_docid(docID, db)
  2224. char* docID;
  2225. database* db;
  2226. {
  2227.   long i;
  2228.   char doc[MAX_FILE_NAME_LEN+50];
  2229.  
  2230.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  2231.     if (get_doc(doc, i, db, FALSE) != NULL) {
  2232.       if (strcmp(doc, docID) == 0) {
  2233.     return (i-1);
  2234.       }
  2235.     }
  2236.   }
  2237.   return -1;
  2238. }
  2239.  
  2240.